package server;

import interfaces.Master;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.rmi.RemoteException;
import java.rmi.server.RemoteServer;
import java.rmi.server.ServerNotActiveException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Random;
import java.util.StringTokenizer;

import Log.*;


public class PrimeMaster implements Master {
    
    private String readFrom = "numbers.txt";
    private String writeTo = "results.txt";
	
    private int jobID = 0;
    private BigInteger N = null;
    private ArrayList<BigInteger> Ns = null;
    private int nIndex = -1;
    private boolean stop = false;
    private Random rand = new Random(1);
    private ArrayList<ComputedJob> computedJobArray = new ArrayList<ComputedJob>();
    private double currentConfidence = 0.0;
    
    private double desiredConfidence = 0.9999999999999999;
    
    // client will stop getJob() when stop=true
    public PrimeMaster() {
        // to be calculated
        Ns = populateNs(readFrom);
        
        // already calculated, if this is a newly elected master after an old one dies
        nIndex = populateNs(writeTo).size() - 1;
    }
    
    public ArrayList<BigInteger> populateNs(String filename) {
        ArrayList<BigInteger> theNs = new ArrayList<BigInteger>();
        try {
			FileReader fp = new FileReader(filename);
			BufferedReader fr = new BufferedReader(fp);
			String line;
			do {
			    line = null;
			    line = fr.readLine();
			    if (line != null) {
			    	StringTokenizer tokens = new StringTokenizer(line);
			    	if (!line.equals("")) {
				        BigInteger bi = new BigInteger(tokens.nextToken());
				        theNs.add(bi);
			        }
			    }			    
			} while (line != null);
			Log.log(MessageType.Master, "Numbers are read.");
			fr.close();
		}
		catch (FileNotFoundException e1) {
			Log.log(MessageType.Master, "Filename not valid. ");
		}
		catch (IOException e2) {
			Log.log(MessageType.Master, "IO Exception occurred.");
		}
        return theNs;
    }
    
    public synchronized void writeResult(BigInteger theNumber, boolean isComposite) {
        try {
			FileWriter fp = new FileWriter(writeTo, true);  // true for append
			BufferedWriter fw = new BufferedWriter(fp);
			
			// og the current time in results.txt
        	DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            Date d = new Date();
            String d_string =  dateFormat.format(d);
            fw.write("Current Time is:" + d_string +" : ");  
			
			fw.write(theNumber.toString() + " " + (isComposite?"composite":"prime"));
			fw.newLine();
			
			fw.close();
		}
		catch (FileNotFoundException e1) {
			Log.log(MessageType.Master, "Filename not valid. ");
		}
		catch (IOException e2) {
			Log.log(MessageType.Master, "IO Exception occurred.");
		}
        return;
    }
    
    private synchronized BigInteger getNextN() {
        // TODO: synchronized?
        stop = false;
        currentConfidence = 0.0;
        nIndex++;
        if (nIndex < Ns.size()) {
        	while (true) {
        		BigInteger tempN = Ns.get(nIndex);
        		if (tempN.compareTo(new BigInteger("3")) < 0) {
        			Log.log(MessageType.Master, "The number " + tempN + " is less than 3!");
        			writeResult(tempN, false);
        			nIndex++;
        		} else { break; }
        	}
        	return Ns.get(nIndex);
        } else {
        	
        	Log.log(MessageType.Master, "All numbers are computed.");
        	
        	// show the time when computation is finished
        	DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            Date d = new Date();
            String d_string =  dateFormat.format(d);
            Log.log(MessageType.Master, d_string);
            try {
    			FileWriter fp = new FileWriter(writeTo, true);  // true for append
    			BufferedWriter fw = new BufferedWriter(fp);
    			fw.write("Stop Time is:" + d_string);
    			fw.newLine();
    			fw.close();
    		}
    		catch (FileNotFoundException e1) {
    			Log.log(MessageType.Master, "Filename not valid. ");
    		}
    		catch (IOException e2) {
    			Log.log(MessageType.Master, "IO Exception occurred.");
    		}
            
            
            
        	try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        	return null;
        }
    }
    
    public synchronized Job getJob() {
        if (N == null) {
            setN(getNextN());
        }
           	
    	if ((N == null) || (stop == true)) {
    		try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    	}
    	
    	BigInteger a;
    	do {
    	    a = new BigInteger(N.bitLength(), rand);
    	} while (a.compareTo(N) != -1 || a.equals(new BigInteger("0")));  // a has to be smaller than N
        
    	Job j = new Job(++jobID,a,N);
    	
        try {
			Log.log(MessageType.Master, "Assign Job :"+ jobID +" (a:" + a + " n:" + N + ") to client:" 
					+ RemoteServer.getClientHost() + " Confidence:" + currentConfidence);
		} catch (ServerNotActiveException e) {
			Log.log(MessageType.Master, "Assigning job failed: remote client is not reacheable.");
		}
    		
        return j;
    }
   
    public synchronized void returnResult(Job j, boolean isComposite) {
    	if (!j.N.equals(N)) {
    		return;
    	}
    	
        //checking if confidence level has reached:
        if (!isComposite) {
            currentConfidence = 1-((1-currentConfidence)*(0.25));
            if ((N.equals(new BigInteger("3")))) {
            	System.err.println("3");
            	writeResult(N, isComposite);
                stop = true;
            }
            if ((N.compareTo((new BigInteger("3"))) > 0) && currentConfidence > desiredConfidence) {
                writeResult(N, isComposite);
                stop = true;
            }
        }
    	
    	computedJobArray.add(new ComputedJob(j.a, j.N, isComposite));
    	
    	try {
			Log.log(MessageType.Master, "Finished Job:"+ j.jobID +", result: "+ isComposite + " from "+ RemoteServer.getClientHost());
		} catch (ServerNotActiveException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	if (isComposite) {
    		Log.log(MessageType.Master, "The number " + N + " is composite!");
    		writeResult(N, isComposite);
    		stop = true;
    		
			setN(getNextN());
			return;
    	}
    	
    	if (stop) {
    		Log.log(MessageType.Master, "The number " + N + " is possibly a prime!");
    		
    		setN(getNextN());
    	}
    	return;
    }
    
    private synchronized void setN(BigInteger N) {
        if (N != null) {
            this.N = N;
            notifyAll();
        }
    }
    
	public void sendComputed(ArrayList<ComputedJob> clientJobs) throws RemoteException {
		
		for (int i=0; i<clientJobs.size(); i++) {
			ComputedJob cj = clientJobs.get(i);
		    if (cj.c_n.equals(this.N) && !computedJobArray.contains(cj)) {
		        computedJobArray.add(cj);
		        currentConfidence = 1-((1-currentConfidence)*(0.25));
		    }
		}
		return;
	}
	
	public void ping() throws RemoteException {
		// Auto-generated method stub
        return;
	}
}
